import sys
import os
import json
import datetime
import socket
import threading
import requests
import csv
import time
import html
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
    QTextEdit, QMenuBar, QAction, QFileDialog, QStatusBar, QScrollArea, QFrame,
    QDialog, QRadioButton, QButtonGroup, QCheckBox, QSpinBox, QGroupBox, QGridLayout, QMessageBox, QComboBox, QMenu
)
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer
from PyQt5.QtGui import QFont, QIcon
from functools import partial

if sys.platform.startswith('win'):
    import ctypes
    import ctypes.wintypes

class SettingsDialog(QDialog):
    def __init__(self, parent, config):
        super().__init__(parent)
        self.setWindowTitle("系统设置")
        self.setMinimumWidth(520)
        self.config = config.copy()
        self.init_ui()
        self.setStyleSheet('''
            QDialog { background: #f6f8fa; }
            QLabel { font-size: 11pt; }
            QLineEdit, QSpinBox, QComboBox {
                background: #fff; border: 1px solid #bfc7d5; border-radius: 5px; padding: 3px; font-size: 11pt;
            }
            QCheckBox, QRadioButton { font-size: 11pt; }
            QPushButton { background: #1976d2; color: #fff; border-radius: 5px; padding: 5px 16px; font-size: 11pt; }
            QPushButton:hover { background: #1565c0; }
        ''')

    def init_ui(self):
        layout = QVBoxLayout(self)
        # 运行模式
        mode_group = QGroupBox("运行模式")
        mode_layout = QHBoxLayout()
        self.mode_btns = {}
        self.mode_btn_group = QButtonGroup(self)
        for i, (mode, text) in enumerate({"server": "服务器模式", "client": "客户端模式", "keyboard": "本地模式"}.items()):
            btn = QRadioButton(text)
            if self.config["mode"] == mode:
                btn.setChecked(True)
            self.mode_btn_group.addButton(btn, i)
            self.mode_btns[mode] = btn
            mode_layout.addWidget(btn)
        mode_group.setLayout(mode_layout)
        layout.addWidget(mode_group)

        # 本地IP设置（仅客户端模式显示）
        self.client_ip_group = QGroupBox("本地IP设置")
        client_ip_layout = QHBoxLayout()
        client_ip_layout.addWidget(QLabel("本机IP:"))
        self.client_ip_edit = QLineEdit(self.config.get("client_ip", ""))
        self.client_ip_edit.setPlaceholderText("本机IP地址")
        client_ip_layout.addWidget(self.client_ip_edit)
        self.client_ip_group.setLayout(client_ip_layout)
        layout.addWidget(self.client_ip_group)
        # 根据当前模式控制显示
        self.update_client_ip_visibility()
        # 绑定模式切换事件
        self.mode_btn_group.buttonClicked.connect(self.update_client_ip_visibility)

        # API设置
        api_group = QGroupBox("API设置")
        api_layout = QGridLayout()
        self.api_enabled_cb = QCheckBox("启用API发送")
        self.api_enabled_cb.setChecked(self.config.get("api_enabled", False))
        api_layout.addWidget(self.api_enabled_cb, 0, 0, 1, 2)
        self.disable_proxy_cb = QCheckBox("禁用代理")
        self.disable_proxy_cb.setChecked(self.config.get("disable_proxy", True))
        api_layout.addWidget(self.disable_proxy_cb, 0, 2, 1, 1)
        api_layout.addWidget(QLabel("请求URL:"), 1, 0)
        self.api_url_edit = QLineEdit(self.config.get("api_url", ""))
        api_layout.addWidget(self.api_url_edit, 1, 1)
        api_layout.addWidget(QLabel("产线编号:"), 2, 0)
        self.line_code_edit = QLineEdit(self.config.get("line_code", ""))
        api_layout.addWidget(self.line_code_edit, 2, 1)
        api_layout.addWidget(QLabel("工位编号:"), 3, 0)
        self.station_code_edit = QLineEdit(self.config.get("station_code", ""))
        api_layout.addWidget(self.station_code_edit, 3, 1)
        api_group.setLayout(api_layout)
        layout.addWidget(api_group)

        # 判断设置
        judge_group = QGroupBox("判断设置")
        judge_layout = QGridLayout()
        self.need_result_cb = QCheckBox("需要判断结果")
        self.need_result_cb.setChecked(self.config.get("need_result", True))
        judge_layout.addWidget(self.need_result_cb, 0, 0)
        self.need_confirm_cb = QCheckBox("需要判断确认")
        self.need_confirm_cb.setChecked(self.config.get("need_confirm", True))
        judge_layout.addWidget(self.need_confirm_cb, 0, 1)
        self.auto_enter_confirm_cb = QCheckBox("自动回车确认")
        self.auto_enter_confirm_cb.setChecked(self.config.get("auto_enter_confirm", False))
        judge_layout.addWidget(self.auto_enter_confirm_cb, 0, 2)
        judge_layout.addWidget(QLabel("判断字符1:"), 1, 0)
        self.judge_char1_edit = QLineEdit(list(self.config["judge_chars"].keys())[0])
        judge_layout.addWidget(self.judge_char1_edit, 1, 1)
        judge_layout.addWidget(QLabel("判断结果1:"), 1, 2)
        self.judge_result1_edit = QLineEdit(list(self.config["judge_chars"].values())[0])
        judge_layout.addWidget(self.judge_result1_edit, 1, 3)
        judge_layout.addWidget(QLabel("判断字符2:"), 2, 0)
        self.judge_char2_edit = QLineEdit(list(self.config["judge_chars"].keys())[1])
        judge_layout.addWidget(self.judge_char2_edit, 2, 1)
        judge_layout.addWidget(QLabel("判断结果2:"), 2, 2)
        self.judge_result2_edit = QLineEdit(list(self.config["judge_chars"].values())[1])
        judge_layout.addWidget(self.judge_result2_edit, 2, 3)
        judge_group.setLayout(judge_layout)
        layout.addWidget(judge_group)

        # 扫码接收格式
        format_group = QGroupBox("扫码/判断接收格式")
        format_layout = QHBoxLayout()
        self.scan_format_combo = QComboBox()
        self.scan_format_combo.addItems(["10进制", "16进制"])
        self.scan_format_combo.setCurrentText(self.config.get("scan_receive_format", "10进制"))
        format_layout.addWidget(QLabel("扫码接收格式:"))
        format_layout.addWidget(self.scan_format_combo)
        self.result_format_combo = QComboBox()
        self.result_format_combo.addItems(["10进制", "16进制"])
        self.result_format_combo.setCurrentText(self.config.get("result_receive_format", "10进制"))
        format_layout.addWidget(QLabel("判断接收格式:"))
        format_layout.addWidget(self.result_format_combo)
        format_group.setLayout(format_layout)
        layout.addWidget(format_group)

        # 强制英文
        self.force_english_cb = QCheckBox("强制英文状态")
        self.force_english_cb.setChecked(self.config.get("force_english", False))
        layout.addWidget(self.force_english_cb)

        # 防重码
        anti_group = QGroupBox("防重码设置")
        anti_layout = QHBoxLayout()
        self.anti_duplicate_cb = QCheckBox("启用防重码")
        self.anti_duplicate_cb.setChecked(self.config.get("anti_duplicate_enabled", False))
        anti_layout.addWidget(self.anti_duplicate_cb)
        anti_layout.addWidget(QLabel("防重码秒数:"))
        self.anti_duplicate_spin = QSpinBox()
        self.anti_duplicate_spin.setRange(1, 60)
        self.anti_duplicate_spin.setValue(self.config.get("anti_duplicate_seconds", 3))
        anti_layout.addWidget(self.anti_duplicate_spin)
        self.anti_duplicate_last_only_cb = QCheckBox("仅与上一个码对比防重码")
        self.anti_duplicate_last_only_cb.setChecked(self.config.get("anti_duplicate_last_only", False))
        anti_layout.addWidget(self.anti_duplicate_last_only_cb)
        anti_group.setLayout(anti_layout)
        layout.addWidget(anti_group)

        # 扫码枪管理
        scanner_group = QGroupBox("扫码枪管理")
        scanner_layout = QVBoxLayout()
        self.scanner_rows = []
        for scanner in self.config["scanners"]:
            self.add_scanner_row(scanner_layout, scanner)
        btn_layout = QHBoxLayout()
        add_btn = QPushButton("添加扫码枪")
        add_btn.clicked.connect(lambda: self.add_scanner_row(scanner_layout, {"ip": "", "port": 8888, "name": ""}))
        btn_layout.addWidget(add_btn)
        del_btn = QPushButton("移除扫码枪")
        del_btn.clicked.connect(lambda: self.remove_scanner_row(scanner_layout))
        btn_layout.addWidget(del_btn)
        scanner_layout.addLayout(btn_layout)
        scanner_group.setLayout(scanner_layout)
        layout.addWidget(scanner_group)

        # 保存按钮
        save_btn = QPushButton("保存设置")
        save_btn.clicked.connect(self.save_settings)
        layout.addWidget(save_btn)

    def add_scanner_row(self, parent_layout, scanner):
        row_layout = QHBoxLayout()
        ip_edit = QLineEdit(scanner.get("ip", ""))
        ip_edit.setPlaceholderText("IP地址/名称")
        port_edit = QLineEdit(str(scanner.get("port", 8888)))
        port_edit.setPlaceholderText("端口号")
        name_edit = QLineEdit(scanner.get("name", ""))
        name_edit.setPlaceholderText("扫码枪名称（本地模式）")
        row_layout.addWidget(QLabel("IP/名称:"))
        row_layout.addWidget(ip_edit)
        row_layout.addWidget(QLabel("端口:"))
        row_layout.addWidget(port_edit)
        row_layout.addWidget(QLabel("名称:"))
        row_layout.addWidget(name_edit)
        parent_layout.addLayout(row_layout)
        self.scanner_rows.append((ip_edit, port_edit, name_edit, row_layout))

    def remove_scanner_row(self, parent_layout):
        if self.scanner_rows:
            ip_edit, port_edit, name_edit, row_layout = self.scanner_rows.pop()
            while row_layout.count():
                item = row_layout.takeAt(0)
                widget = item.widget()
                if widget:
                    widget.deleteLater()

    def save_settings(self):
        # 运行模式
        for mode, btn in self.mode_btns.items():
            if btn.isChecked():
                self.config["mode"] = mode
        # 保存本地IP设置
        self.config["client_ip"] = self.client_ip_edit.text().strip()
        self.config["api_enabled"] = self.api_enabled_cb.isChecked()
        self.config["disable_proxy"] = self.disable_proxy_cb.isChecked()
        self.config["api_url"] = self.api_url_edit.text().strip()
        self.config["line_code"] = self.line_code_edit.text().strip()
        self.config["station_code"] = self.station_code_edit.text().strip()
        self.config["need_result"] = self.need_result_cb.isChecked()
        self.config["need_confirm"] = self.need_confirm_cb.isChecked()
        self.config["auto_enter_confirm"] = self.auto_enter_confirm_cb.isChecked()
        self.config["judge_chars"] = {
            self.judge_char1_edit.text().strip(): self.judge_result1_edit.text().strip(),
            self.judge_char2_edit.text().strip(): self.judge_result2_edit.text().strip()
        }
        self.config["scan_receive_format"] = self.scan_format_combo.currentText()
        self.config["result_receive_format"] = self.result_format_combo.currentText()
        self.config["force_english"] = self.force_english_cb.isChecked()
        self.config["anti_duplicate_enabled"] = self.anti_duplicate_cb.isChecked()
        self.config["anti_duplicate_seconds"] = self.anti_duplicate_spin.value()
        self.config["anti_duplicate_last_only"] = self.anti_duplicate_last_only_cb.isChecked()
        # 扫码枪
        scanners = []
        for ip_edit, port_edit, name_edit, _ in self.scanner_rows:
            ip = ip_edit.text().strip()
            port = port_edit.text().strip()
            name = name_edit.text().strip()
            if ip or name:
                try:
                    port = int(port)
                except ValueError:
                    port = 8888
                scanners.append({"ip": ip, "port": port, "name": name})
        self.config["scanners"] = scanners
        self.accept()

    def update_client_ip_visibility(self):
        """根据当前选择的模式控制本地IP设置的显示/隐藏"""
        current_mode = None
        for mode, btn in self.mode_btns.items():
            if btn.isChecked():
                current_mode = mode
                break
        
        if current_mode == "client":
            self.client_ip_group.setVisible(True)
        else:
            self.client_ip_group.setVisible(False)

class ScanDataSignal(QObject):
    scan_received = pyqtSignal(str, int)  # 条码, 扫码枪索引
    scanner_status_signal = pyqtSignal(int, str)  # 扫码枪索引, 状态

    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window

    def update_history_red(self, msg):
        QTimer.singleShot(0, lambda: self._update_history_red(msg))

    def update_history_green(self, msg):
        QTimer.singleShot(0, lambda: self._update_history_green(msg))

    def _update_history_red(self, msg):
        if self.main_window and hasattr(self.main_window, 'history_text'):
            escaped_msg = html.escape(str(msg))
            self.main_window.history_text.append(f'<span style=\"color:red;\">{escaped_msg}</span>')
            self.main_window.history_text.moveCursor(self.main_window.history_text.textCursor().End)

    def _update_history_green(self, msg):
        if self.main_window and hasattr(self.main_window, 'history_text'):
            escaped_msg = html.escape(str(msg))
            self.main_window.history_text.append(f'<span style=\"color:green;\">{escaped_msg}</span>')
            self.main_window.history_text.moveCursor(self.main_window.history_text.textCursor().End)

class TCPConnectionThread(threading.Thread):
    def __init__(self, mode, scanners, scan_signal, config):
        super().__init__()
        self.mode = mode
        self.scanners = scanners
        self.scan_signal = scan_signal
        self.config = config
        self.running = True
        self.sockets = []

    def run(self):
        try:
            if self.mode == "server":
                self.run_server()
            elif self.mode == "client":
                self.run_clients()
        except Exception as e:
            print(f"TCP连接线程运行异常: {e}")

    def run_server(self):
        # 监听所有扫码枪端口
        for idx, scanner in enumerate(self.scanners):
            port = scanner.get("port", 8888)
            t = threading.Thread(target=self.server_listen, args=(port, idx))
            t.daemon = True
            t.start()

    def server_listen(self, port, idx):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind(("", port))
        s.listen(1)
        while self.running:
            try:
                client, addr = s.accept()
                self.scan_signal.scanner_status_signal.emit(idx, "已连接")
                with client:
                    while self.running:
                        try:
                            data = client.recv(1024)
                            if not data:
                                self.scan_signal.scanner_status_signal.emit(idx, "断开")
                                break
                            # 判断扫码/判断接收格式
                            if self.config.get("need_result", True):
                                if idx % 2 == 0:
                                    # 扫码接收格式
                                    if self.config.get("scan_receive_format", "10进制") == "16进制":
                                        barcode = data.hex()
                                    else:
                                        barcode = data.decode(errors='ignore').strip()
                                else:
                                    # 判断接收格式
                                    if self.config.get("result_receive_format", "10进制") == "16进制":
                                        barcode = data.hex()
                                    else:
                                        barcode = data.decode(errors='ignore').strip()
                            else:
                                # 无需判断结果时，全部按扫码接收格式
                                if self.config.get("scan_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                            self.scan_signal.scan_received.emit(barcode, idx)
                        except Exception:
                            self.scan_signal.scanner_status_signal.emit(idx, "断开")
                            break
            except Exception:
                self.scan_signal.scanner_status_signal.emit(idx, "异常")
                continue
        s.close()

    def run_clients(self):
        # 每个扫码枪一个连接
        try:
            for idx, scanner in enumerate(self.scanners):
                t = threading.Thread(target=self.client_connect, args=(scanner, idx))
                t.daemon = True
                t.start()
        except Exception as e:
            print(f"启动客户端连接线程异常: {e}")

    def client_connect(self, scanner, idx):
        ip = scanner.get("ip")
        port = scanner.get("port", 8888)
        while self.running:
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                # 设置socket超时，避免长时间阻塞
                s.settimeout(5)
                s.connect((ip, port))
                self.scan_signal.scanner_status_signal.emit(idx, "已连接")
                while self.running:
                    try:
                        data = s.recv(1024)
                        if not data:
                            self.scan_signal.scanner_status_signal.emit(idx, "断开")
                            break
                        # 判断扫码/判断接收格式
                        if self.config.get("need_result", True):
                            if idx % 2 == 0:
                                # 扫码接收格式
                                if self.config.get("scan_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                            else:
                                # 判断接收格式
                                if self.config.get("result_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                        else:
                            # 无需判断结果时，全部按扫码接收格式
                            if self.config.get("scan_receive_format", "10进制") == "16进制":
                                barcode = data.hex()
                            else:
                                barcode = data.decode(errors='ignore').strip()
                        self.scan_signal.scan_received.emit(barcode, idx)
                    except socket.timeout:
                        # 超时异常，继续循环
                        continue
                    except socket.error as e:
                        # 网络异常
                        print(f"扫码枪{idx+1}网络异常: {e}")
                        self.scan_signal.scanner_status_signal.emit(idx, "断开")
                        break
                    except Exception as e:
                        # 其他异常
                        print(f"扫码枪{idx+1}未知异常: {e}")
                        self.scan_signal.scanner_status_signal.emit(idx, "异常")
                        break
                s.close()
            except socket.timeout:
                # 连接超时
                print(f"扫码枪{idx+1}连接超时: {ip}:{port}")
                self.scan_signal.scanner_status_signal.emit(idx, "连接超时")
            except socket.error as e:
                # 网络连接异常
                print(f"扫码枪{idx+1}连接失败: {ip}:{port}, 错误: {e}")
                self.scan_signal.scanner_status_signal.emit(idx, "异常")
            except Exception as e:
                # 其他未知异常
                print(f"扫码枪{idx+1}未知连接异常: {e}")
                self.scan_signal.scanner_status_signal.emit(idx, "异常")
            finally:
                # 确保socket被关闭
                try:
                    if 's' in locals():
                        s.close()
                except Exception:
                    pass
            threading.Event().wait(2)

    def stop(self):
        self.running = False
        # 关闭所有socket
        for s in self.sockets:
            try:
                s.close()
            except Exception:
                pass

class CustomScanLineEdit(QLineEdit):
    def __init__(self, force_english=False, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.force_english = force_english

    def focusInEvent(self, event):
        super().focusInEvent(event)
        if self.force_english:
            self.set_english_input_method()

    def set_english_input_method(self):
        if sys.platform.startswith('win'):
            hwnd = int(self.winId())
            hIMC = ctypes.windll.imm32.ImmGetContext(hwnd)
            # 0x0000: 英文(半角), 0x0001: 中文(全角)
            ctypes.windll.imm32.ImmSetConversionStatus(hIMC, 0x0000, 0x0000)
            ctypes.windll.imm32.ImmReleaseContext(hwnd, hIMC)

class MainWindow(QMainWindow):
    api_result_signal = pyqtSignal(str, str)  # (msg, color)
    def __init__(self):
        super().__init__()
        self.setWindowTitle("扫码记录系统")
        self.setWindowIcon(QIcon("favicon.ico"))
        self.resize(800, 600)
        self.config = self.load_config()
        self.init_ui()
        self.apply_styles()
        self.api_result_signal.connect(self.append_api_result)
        self.scan_signal = ScanDataSignal(self)
        self.scan_signal.scan_received.connect(self.handle_external_scan)
        self.scan_signal.scanner_status_signal.connect(self.update_scanner_status)
        self.tcp_thread = None
        self.start_tcp_connections()
        self.history_records = []
        self.serial_number = 1
        self._empty_warned = False  # 新增：无数据只提示一次

    def load_config(self):
        config_file = "scan_config.json"
        default_config = {
            "mode": "client",
            "save_path": "D:/条码记录",
            "scanners": [],
            "need_result": True,
            "need_confirm": True,
            "judge_chars": {"1": "OK", "0": "NG"},
            "client_ip": self.get_local_ip(),
            "scan_receive_format": "10进制",
            "result_receive_format": "10进制",
            "force_english": False,
            "api_enabled": False,
            "api_url": "http://127.0.0.1:5000/api/reportApi/report",
            "line_code": "",
            "station_code": "",
            "anti_duplicate_enabled": False,
            "anti_duplicate_seconds": 3,
            "disable_proxy": True
        }
        if os.path.exists(config_file):
            try:
                with open(config_file, "r", encoding="utf-8") as f:
                    config = json.load(f)
                for key in default_config:
                    if key not in config:
                        config[key] = default_config[key]
                return config
            except Exception as e:
                print(f"加载配置文件出错: {e}")
                return default_config
        else:
            return default_config

    def get_local_ip(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            local_ip = s.getsockname()[0]
            s.close()
            return local_ip
        except Exception:
            return "127.0.0.1"

    def init_ui(self):
        # 菜单栏
        menu_bar = QMenuBar(self)
        self.setMenuBar(menu_bar)
        settings_action = QAction("设置", self)
        settings_action.triggered.connect(self.open_settings)
        menu_bar.addAction(settings_action)

        # 主部件
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)
        main_layout.setContentsMargins(16, 16, 16, 8)
        main_layout.setSpacing(12)

        # 标题栏
        self.title_label = QLabel(self.get_title_text())
        self.title_label.setFont(QFont("微软雅黑", 16, QFont.Bold))
        self.title_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(self.title_label)

        # 扫码区（动态生成，后续补充）
        self.scan_area = QFrame()
        self.scan_area.setFrameShape(QFrame.StyledPanel)
        self.scan_area_layout = QVBoxLayout(self.scan_area)
        self.scan_area_layout.setSpacing(8)
        main_layout.addWidget(self.scan_area)
        self.scan_entries = []
        self.result_entries = []
        self.switch_buttons = []
        self.keyboard_modes = []
        self.build_scan_widgets()

        # 历史区
        history_layout = QHBoxLayout()
        history_label = QLabel("历史记录:")
        history_label.setFont(QFont("微软雅黑", 11))
        self.history_text = QTextEdit()
        self.history_text.setReadOnly(True)
        self.history_text.setMinimumHeight(120)
        self.history_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.history_text.customContextMenuRequested.connect(self.show_history_menu)
        history_layout.addWidget(history_label)
        history_layout.addWidget(self.history_text, 1)
        main_layout.addLayout(history_layout)

        # 路径区
        path_layout = QHBoxLayout()
        path_label = QLabel("保存路径:")
        self.path_entry = QLineEdit(self.config["save_path"])
        self.choose_path_btn = QPushButton("选择路径")
        self.open_path_btn = QPushButton("打开记录文件夹")
        self.choose_path_btn.clicked.connect(self.choose_save_path)
        self.open_path_btn.clicked.connect(self.open_save_path)
        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_entry, 1)
        path_layout.addWidget(self.choose_path_btn)
        path_layout.addWidget(self.open_path_btn)
        main_layout.addLayout(path_layout)

        # 状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_label = QLabel("")
        self.status_bar.addWidget(self.status_label)

    def get_title_text(self):
        mode_zh = {"server": "服务器模式", "client": "客户端模式", "keyboard": "本地模式"}.get(self.config.get("mode"), "未知模式")
        if self.config.get("api_enabled", False):
            line_code = self.config.get("line_code", "")
            station_code = self.config.get("station_code", "")
            return f"{line_code}-{station_code}-扫码系统-{mode_zh}"
        else:
            return f"扫码系统-{mode_zh}"

    def open_settings(self):
        dlg = SettingsDialog(self, self.config)
        if dlg.exec_() == QDialog.Accepted:
            self.config = dlg.config
            self.save_config()
            self.update_ui_by_config()

    def save_config(self):
        try:
            with open("scan_config.json", "w", encoding="utf-8") as f:
                json.dump(self.config, f, ensure_ascii=False, indent=2)
        except Exception as e:
            QMessageBox.warning(self, "保存失败", f"保存配置文件失败: {e}")

    def update_ui_by_config(self):
        # 更新标题、路径等，扫码区动态刷新
        self.title_label.setText(self.get_title_text())
        self.path_entry.setText(self.config.get("save_path", ""))
        self.build_scan_widgets()
        self.start_tcp_connections()
        self.path_entry.setText(self.config.get("save_path", ""))

    def apply_styles(self):
        self.setStyleSheet('''
            QMainWindow {
                background: #f6f8fa;
            }
            QLabel {
                color: #222;
            }
            QLineEdit, QTextEdit {
                background: #fff;
                border: 1px solid #bfc7d5;
                border-radius: 5px;
                padding: 4px;
                font-size: 12pt;
            }
            QPushButton {
                background: #1976d2;
                color: #fff;
                border-radius: 5px;
                padding: 6px 16px;
                font-size: 11pt;
            }
            QPushButton:hover {
                background: #1565c0;
            }
            QMenuBar {
                background: #1976d2;
                color: #fff;
                font-size: 11pt;
            }
            QMenuBar::item:selected {
                background: #1565c0;
            }
            QStatusBar QLabel {
                color: #1976d2;
                font-size: 10pt;
            }
        ''')

    def build_scan_widgets(self):
        # 清空原有控件
        for i in reversed(range(self.scan_area_layout.count())):
            item = self.scan_area_layout.takeAt(i)
            widget = item.widget()
            if widget:
                widget.deleteLater()
        self.scan_entries = []
        self.result_entries = []
        self.switch_buttons = []
        self.keyboard_modes = []
        # 动态生成扫码框
        need_result = self.config.get("need_result", True)
        scanners = self.config.get("scanners", [])
        force_english = self.config.get("force_english", False)
        if need_result:
            num = len(scanners) // 2
        else:
            num = len(scanners)
        for i in range(num):
            row = QHBoxLayout()
            scan_label = QLabel(f"扫码{i+1}:")
            scan_label.setMinimumWidth(60)
            row.addWidget(scan_label)
            scan_entry = CustomScanLineEdit(force_english=force_english)
            scan_entry.setPlaceholderText("请扫码...")
            scan_entry.setMinimumWidth(220)
            if self.config.get("auto_enter_confirm", False) and need_result:
                scan_entry.returnPressed.connect(partial(self.handle_result, i))
            else:
                scan_entry.returnPressed.connect(partial(self.handle_scan, i))
            self.scan_entries.append(scan_entry)
            row.addWidget(scan_entry)
            switch_btn = QPushButton("键盘")
            switch_btn.setCheckable(True)
            switch_btn.clicked.connect(partial(self.switch_input_mode, i))
            self.switch_buttons.append(switch_btn)
            self.keyboard_modes.append(False)
            row.addWidget(switch_btn)
            if need_result:
                result_label = QLabel(f"判断{i+1}:")
                result_label.setMinimumWidth(60)
                row.addWidget(result_label)
                result_entry = QLineEdit()
                result_entry.setPlaceholderText("输入判断...")
                result_entry.setMinimumWidth(80)
                # 只绑定一种事件，绝不重复
                if self.config.get("need_confirm", True):
                    result_entry.returnPressed.connect(partial(self.handle_result, i))
                else:
                    result_entry.textChanged.connect(partial(self.handle_result, i))
                self.result_entries.append(result_entry)
                row.addWidget(result_entry)
            self.scan_area_layout.addLayout(row)
        if self.scan_entries:
            self.scan_entries[0].setFocus()

    def switch_input_mode(self, index):
        self.keyboard_modes[index] = not self.keyboard_modes[index]
        if self.keyboard_modes[index]:
            self.switch_buttons[index].setText("扫码")
        else:
            self.switch_buttons[index].setText("键盘")

    def handle_scan(self, index):
        if self.config.get("need_result", True):
            if index < len(self.result_entries):
                self.result_entries[index].setFocus()
                if not self.config.get("need_confirm", True):
                    scan_code = self.scan_entries[index].text().strip()
                    result_char = self.result_entries[index].text().strip()
                    result_text = self.config["judge_chars"].get(result_char, "未知结果")
                    if not self.save_record(scan_code, result_text):
                        if not self._empty_warned:
                            self.append_history_red("扫码框无数据，未保存和上传！")
                            self._empty_warned = True
                        return
                    self._empty_warned = False
        else:
            scan_code = self.scan_entries[index].text().strip()
            result_text = self.get_true_result()
            if not self.save_record(scan_code, result_text):
                if not self._empty_warned:
                    self.append_history_red("扫码框无数据，未保存和上传！")
                    self._empty_warned = True
                return
            self._empty_warned = False
            self.focus_next_scan(index)

    def handle_result(self, index):
        result_char = self.result_entries[index].text().strip()
        scan_code = self.scan_entries[index].text().strip()
        result_text = self.config["judge_chars"].get(result_char, "未知结果")
        if result_char in self.config["judge_chars"]:
            if not self.save_record(scan_code, result_text):
                if not self._empty_warned:
                    self.append_history_red("扫码框无数据，未保存和上传！")
                    self._empty_warned = True
                return
            self._empty_warned = False
            self.focus_next_scan(index)

    def focus_next_scan(self, current_index):
        next_index = current_index + 1
        if next_index < len(self.scan_entries):
            self.scan_entries[next_index].setFocus()
        else:
            self.scan_entries[0].setFocus()

    def start_tcp_connections(self):
        try:
            # 关闭旧线程
            if hasattr(self, 'tcp_thread') and self.tcp_thread:
                self.tcp_thread.stop()
            mode = self.config.get("mode", "client")
            if mode == "keyboard":
                return
            scanners = self.config.get("scanners", [])
            if not scanners:
                print("没有配置扫码枪，跳过TCP连接启动")
                return
            self.tcp_thread = TCPConnectionThread(mode, scanners, self.scan_signal, self.config)
            self.tcp_thread.daemon = True
            self.tcp_thread.start()
            print(f"TCP连接启动成功，模式: {mode}, 扫码枪数量: {len(scanners)}")
        except Exception as e:
            print(f"启动TCP连接时出现异常: {e}")
            self.scan_signal.update_history_red(f"TCP连接启动失败: {e}")

    def handle_external_scan(self, barcode, idx):
        # 外部扫码枪数据到达，自动填入对应扫码框
        try:
            if not barcode:
                self.scan_signal.update_history_red("扫码枪数据为空，已忽略！")
                return
            need_result = self.config.get("need_result", True)
            if need_result:
                scan_group = idx // 2
                if scan_group < len(self.scan_entries):
                    if self.keyboard_modes[scan_group]:
                        return
                    if idx % 2 == 0:
                        # 偶数：扫码输入框
                        self.scan_entries[scan_group].setText(barcode)
                        if self.config.get("auto_enter_confirm", False):
                            self.handle_result(scan_group)
                        else:
                            self.result_entries[scan_group].setFocus()
                    else:
                        # 奇数：判断输入框
                        self.result_entries[scan_group].setText(barcode)
                        self.handle_result(scan_group)
            else:
                scan_group = idx
                if scan_group < len(self.scan_entries):
                    if self.keyboard_modes[scan_group]:
                        return
                    self.scan_entries[scan_group].setText(barcode)
                    result_text = self.get_true_result()
                    self.save_record(barcode, result_text)
                    self.focus_next_scan(scan_group)
        except Exception as e:
            # 捕获所有异常，防止程序崩溃
            print(f"处理外部扫码数据异常: {e}")
            self.scan_signal.update_history_red(f"扫码数据处理异常: {e}")

    def choose_save_path(self):
        path = QFileDialog.getExistingDirectory(self, "选择保存路径", self.path_entry.text())
        if path:
            self.path_entry.setText(path)
            self.config["save_path"] = path
            self.save_config()

    def open_save_path(self):
        path = self.path_entry.text()
        if os.path.exists(path):
            if sys.platform.startswith('win'):
                os.startfile(path)
            elif sys.platform.startswith('darwin'):
                os.system(f'open "{path}"')
            else:
                os.system(f'xdg-open "{path}"')
        else:
            QMessageBox.warning(self, "错误", "路径不存在")

    def save_record(self, barcode, result, save_to_file=True, check_duplicate=True, send_api=True, show_record=True):
        # 检查扫码框是否有数据
        if not barcode:
            return False
        # 防重码判断
        if self.config.get("anti_duplicate_enabled", False) and check_duplicate:
            now_time = time.time()
            if not hasattr(self, 'last_scan_times'):
                self.last_scan_times = {}
            interval = self.config.get("anti_duplicate_seconds", 3)
            if self.config.get("anti_duplicate_last_only", False):
                # 仅与上一个码对比
                if not hasattr(self, 'last_barcode'):
                    self.last_barcode = None
                    self.last_barcode_time = 0
                if barcode == getattr(self, 'last_barcode', None) and now_time - getattr(self, 'last_barcode_time', 0) < interval:
                    self.append_history_red(f"防重码：{interval}秒内与上一个码重复，已忽略")
                    return False
                self.last_barcode = barcode
                self.last_barcode_time = now_time
            else:
                last_time = self.last_scan_times.get(barcode, 0)
                if now_time - last_time < interval:
                    self.append_history_red(f"防重码：{interval}秒内重复扫码，已忽略")
                    return False
                self.last_scan_times[barcode] = now_time
        
        # API上传（如果启用）
        if self.config.get("api_enabled", False) and send_api:
            # 同步执行API请求，等待结果
            api_success = self.send_api_request_sync(barcode, result)
            if not api_success:
                # API失败，不记录任何数据
                self.reset_inputs()
                return False
        
        # 只有当show_record为True时才显示记录到历史
        if show_record:
            now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            record = f"{now} - 条码: {barcode}, 结果: {result}"
            self.history_records.append(record)
            if len(self.history_records) > 13:
                self.history_records.pop(0)
            # 黑色显示主记录
            escaped_record = html.escape(record)
            self.history_text.append(f'<span style="color:black;">{escaped_record}</span>')
            self.history_text.moveCursor(self.history_text.textCursor().End)
        
        # 只有当save_to_file为True时才保存到CSV文件
        if save_to_file:
            self.save_to_csv(barcode, result)
        
        self.reset_inputs()
        return True

    def append_history_red(self, msg):
        # 在历史区插入红字（线程安全）
        escaped_msg = html.escape(str(msg))
        self.history_text.append(f'<span style="color:red;">{escaped_msg}</span>')
        self.history_text.moveCursor(self.history_text.textCursor().End)

    def get_true_result(self):
        # 优先找"OK""合格""阳性""通过"等正向含义
        for k, v in self.config["judge_chars"].items():
            if v.strip().lower() in ["ok", "合格", "阳性", "通过", "true", "pass"]:
                return v
        # 找不到就用第一个
        return list(self.config["judge_chars"].values())[0]

    def append_api_result(self, msg, color):
        escaped_msg = html.escape(str(msg))
        if color == "red":
            self.history_text.append(f'<span style="color:red;">{escaped_msg}</span>')
        elif color == "green":
            self.history_text.append(f'<span style="color:green;">{escaped_msg}</span>')
        elif color == "blue":
            self.history_text.append(f'<span style="color:blue;">{escaped_msg}</span>')
        elif color == "orange":
            self.history_text.append(f'<span style="color:orange;">{escaped_msg}</span>')
        elif color == "black":
            self.history_text.append(f'<span style="color:black;">{escaped_msg}</span>')
        else:
            self.history_text.append(f'<span style="color:black;">{escaped_msg}</span>')
        self.history_text.moveCursor(self.history_text.textCursor().End)

    def handle_business_error(self, err_code, err_message, barcode):
        """处理业务错误信息"""
        # 只显示错误信息
        self.api_result_signal.emit(f"❌ {err_message}", "red")

    def send_api_request_sync(self, barcode, result):
        """同步发送API请求，返回是否成功"""
        url = self.config.get("api_url", "")
        line_code = self.config.get("line_code", "")
        station_code = self.config.get("station_code", "")
        if not url or not line_code or not station_code:
            self.api_result_signal.emit("API参数未配置完整，未上传！", "red")
            return False
        
        data = {
            "lineCode": line_code,
            "stationCode": station_code,
            "sn": barcode,
            "qty": 1,
            "result": result
        }
        
        try:
            # 设置请求头
            headers = {
                'Content-Type': 'application/json',
                'User-Agent': 'ScanSystem/1.0'
            }
            
            # 设置代理配置
            proxies = None
            if self.config.get("disable_proxy", True):
                proxies = {
                    'http': None,
                    'https': None
                }
            
            # 发送请求
            resp = requests.post(
                url, 
                json=data, 
                headers=headers,
                proxies=proxies,
                timeout=10
            )
            
            try:
                resp_json = resp.json()
            except Exception:
                resp_json = {}
            
            # 判断响应结果
            msg = resp_json.get("msg", "").strip().lower()
            success = resp_json.get("success", None)  # 获取success字段，可能为None
            
            # 只有当success明确为false时才返回失败
            if success is False:
                err_message = resp_json.get("errMessage", "")
                # 只显示错误信息，不记录任何数据
                self.api_result_signal.emit(f"❌ {err_message}", "red")
                return False
            
            # 其他情况按正常流程处理
            if resp.status_code == 200 and (msg in ["success", "ok", ""] or success is True):
                self.api_result_signal.emit(f"✅ API请求成功，SN: {barcode}", "green")
                return True
            else:
                self.api_result_signal.emit(f"❌ API请求失败，状态码: {resp.status_code}，SN: {barcode}", "red")
                return False
                
        except requests.exceptions.Timeout:
            self.api_result_signal.emit(f"⏰ API请求超时，SN: {barcode}", "red")
            return False
        except requests.exceptions.ConnectionError:
            self.api_result_signal.emit(f"🔌 API连接错误，无法连接到服务器，SN: {barcode}", "red")
            return False
        except requests.exceptions.RequestException as e:
            self.api_result_signal.emit(f"❌ API请求异常: {str(e)}，SN: {barcode}", "red")
            return False
        except Exception as e:
            self.api_result_signal.emit(f"💥 API未知错误: {str(e)}，SN: {barcode}", "red")
            return False

    def send_api_request(self, barcode, result):
        url = self.config.get("api_url", "")
        line_code = self.config.get("line_code", "")
        station_code = self.config.get("station_code", "")
        if not url or not line_code or not station_code:
            self.api_result_signal.emit("API参数未配置完整，未上传！", "red")
            return
        
        data = {
            "lineCode": line_code,
            "stationCode": station_code,
            "sn": barcode,
            "qty": 1,
            "result": result
        }
        
        try:
            # 设置请求头
            headers = {
                'Content-Type': 'application/json',
                'User-Agent': 'ScanSystem/1.0'
            }
            
            # 设置代理配置
            proxies = None
            if self.config.get("disable_proxy", True):
                proxies = {
                    'http': None,
                    'https': None
                }
            
            # 发送请求
            resp = requests.post(
                url, 
                json=data, 
                headers=headers,
                proxies=proxies,
                timeout=10
            )
            
            try:
                resp_json = resp.json()
            except Exception:
                resp_json = {}
            
            # 判断响应结果
            msg = resp_json.get("msg", "").strip().lower()
            success = resp_json.get("success", None)  # 获取success字段，可能为None
            
            # 只有当success明确为false时才显示业务错误
            if success is False:
                err_message = resp_json.get("errMessage", "")
                # 只显示错误信息，不记录任何数据
                self.api_result_signal.emit(f"❌ {err_message}", "red")
                # 直接清空输入框，不调用save_record
                self.reset_inputs()
                return
            
            # 其他情况按正常流程处理
            if resp.status_code == 200 and (msg in ["success", "ok", ""] or success is True):
                self.api_result_signal.emit(f"✅ API请求成功，SN: {barcode}", "green")
            else:
                self.api_result_signal.emit(f"❌ API请求失败，状态码: {resp.status_code}，SN: {barcode}", "red")
                
        except requests.exceptions.Timeout:
            self.api_result_signal.emit(f"⏰ API请求超时，SN: {barcode}", "red")
        except requests.exceptions.ConnectionError:
            self.api_result_signal.emit(f"🔌 API连接错误，无法连接到服务器，SN: {barcode}", "red")
        except requests.exceptions.RequestException as e:
            self.api_result_signal.emit(f"❌ API请求异常: {str(e)}，SN: {barcode}", "red")
        except Exception as e:
            self.api_result_signal.emit(f"💥 API未知错误: {str(e)}，SN: {barcode}", "red")

    def save_to_csv(self, barcode, result):
        save_base = self.path_entry.text()
        if not save_base:
            QMessageBox.warning(self, "错误", "请先设置保存路径")
            return
        now = datetime.datetime.now()
        year_dir = now.strftime("%Y")
        month_dir = now.strftime("%m")
        date_str = now.strftime("%y%m%d")
        save_path = os.path.join(save_base, year_dir, month_dir)
        os.makedirs(save_path, exist_ok=True)
        file_name = f"{date_str}.csv"
        file_path = os.path.join(save_path, file_name)
        is_new_file = not os.path.exists(file_path)
        with open(file_path, "a", encoding="utf-8-sig", newline='') as f:
            writer = csv.writer(f)
            if is_new_file:
                headers = ["序号", "时间", "条码"]
                if self.config["need_result"]:
                    headers.append("判断结果")
                writer.writerow(headers)
            row = [self.serial_number, now.strftime('%Y-%m-%d %H:%M:%S'), barcode]
            if self.config["need_result"]:
                row.append(result)
            writer.writerow(row)
            self.serial_number += 1

    def reset_inputs(self):
        for entry in self.scan_entries:
            entry.clear()
        if self.config["need_result"]:
            for entry in self.result_entries:
                entry.clear()

    def update_status_label(self, text, color=None):
        if color:
            self.status_label.setStyleSheet(f"color:{color}")
        else:
            self.status_label.setStyleSheet("")
        self.status_label.setText(text)

    def update_scanner_status(self, idx, status):
        # 多扫码枪状态，右下角显示
        try:
            if not hasattr(self, '_scanner_status'): self._scanner_status = {}
            self._scanner_status[idx] = status
            status_text = ' | '.join([f"扫码枪{(i+1)}:{s}" for i, s in sorted(self._scanner_status.items())])
            self.update_status_label(status_text)
        except Exception as e:
            # 捕获状态更新异常，防止程序崩溃
            print(f"更新扫码枪状态异常: {e}")

    def show_history_menu(self, pos):
        menu = QMenu(self)
        clear_action = menu.addAction("清除记录")
        action = menu.exec_(self.history_text.mapToGlobal(pos))
        if action == clear_action:
            self.clear_history()

    def clear_history(self):
        self.history_records = []
        self.history_text.clear()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_()) 